package com.dhj.yygh.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dhj.yygh.client.HospitalClient;
import com.dhj.yygh.client.UserClient;
import com.dhj.yygh.common.exception.YyghException;
import com.dhj.yygh.common.helper.HttpRequestHelper;
import com.dhj.yygh.common.result.MyResult;
import com.dhj.yygh.common.result.ResultCodeEnum;
import com.dhj.yygh.constant.MQConst;
import com.dhj.yygh.constant.MqConst;
import com.dhj.yygh.enums.OrderStatusEnum;
import com.dhj.yygh.enums.PaymentStatusEnum;
import com.dhj.yygh.enums.PaymentTypeEnum;
import com.dhj.yygh.mapper.OrderInfoMapper;
import com.dhj.yygh.model.hosp.Schedule;
import com.dhj.yygh.model.order.OrderInfo;
import com.dhj.yygh.model.order.PaymentInfo;
import com.dhj.yygh.model.order.RefundInfo;
import com.dhj.yygh.model.user.Patient;
import com.dhj.yygh.service.*;
import com.dhj.yygh.vo.hosp.ScheduleOrderVo;
import com.dhj.yygh.vo.msm.MsmVo;
import com.dhj.yygh.vo.order.*;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;

@Service
@Slf4j
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private HospitalClient hospitalClient;

    @Autowired
    RabbitService rabbitService;

    @Autowired
    OrderInfoMapper orderInfoMapper;

    @Autowired
    WeixinService weixinService;

    @Autowired
    PayInfoService payInfoServicel;

    @Autowired
    RefundService refundService;


    /**
     * 根据排班 id 和就诊人 id 创建订单
     *
     * @param scheduleId 排班 id
     * @param patientId  就诊人 id
     * @return 返回订单号
     */
    /*
    整个预约下订单流程梳理
    1.平台获取就诊人信息，预约下单排班数据
    2.平台从自己的数据库获取对应医院的签名
    3.将 1 获取到的数据封装到订单数据中
    4.将 订单中的相关数据 | 就诊人的相关的数据 | 签名 等统一进行封装，请求发送到对应的医院方
    5.医院验证签名，通过后，将医院自己与订单相关的数据(如取号时间 | 取号地址 | 剩余可预约数等)通过请求发送到平台
    6.平台接受到医院发送的数据，更新到平台的订单中，此时订单还未完成支付
     */
    @Override
    public Long createOrder(String scheduleId, Long patientId) {
        // 根据就诊人 id 获取就诊人信息(已经封装好)
        Patient patient = userClient.getPatientById(patientId);

        // 根据排班 id 获取预约下单排班数据
        ScheduleOrderVo scheduleOrderVo = hospitalClient.getScheduleOrderVo(scheduleId);


        // 当前时间不可以预约(开始挂号之前和之后)
        if (new DateTime(scheduleOrderVo.getStartTime()).isAfterNow()
                || new DateTime(scheduleOrderVo.getEndTime()).isBeforeNow()) {
            throw new YyghException(ResultCodeEnum.TIME_NO);
        }

        // 获取医院签名相关信息
        SignInfoVo signInfoVo = hospitalClient.getSignInfoVo(scheduleOrderVo.getHoscode());

        // 添加数据到订单对象
        OrderInfo orderInfo = new OrderInfo();
        BeanUtils.copyProperties(scheduleOrderVo, orderInfo);// 添加下单排班的详细数据到订单中

        String outTradeNo = System.currentTimeMillis() + "" + new Random().nextInt(100); // 订单交易号
        orderInfo.setOutTradeNo(outTradeNo);
        orderInfo.setHos_schedule_id(scheduleId);
        orderInfo.setUserId(patient.getUserId());
        orderInfo.setPatientId(patientId);
        orderInfo.setPatientName(patient.getName());
        orderInfo.setPatientPhone(patient.getPhone());
        orderInfo.setOrderStatus(OrderStatusEnum.UNPAID.getStatus());// 订单状态待支付
        this.save(orderInfo);

        // 封装就诊人和订单信息
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("hoscode", orderInfo.getHoscode());
        paramMap.put("depcode", orderInfo.getDepcode());
        paramMap.put("reserveDate", new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")); // 安排日期(实际上就是 workDate)
        paramMap.put("reserveTime", orderInfo.getReserveTime()); // 安排时间(0:上午\1:下午)
        paramMap.put("quitTime", scheduleOrderVo.getQuitTime());
        paramMap.put("amount", orderInfo.getAmount());
        paramMap.put("name", patient.getName());
        paramMap.put("certificatesType", patient.getCertificatesType());
        paramMap.put("certificatesNo", patient.getCertificatesNo());
        paramMap.put("sex", patient.getSex());
        paramMap.put("birthdate", patient.getBirthdate());
        paramMap.put("phone", patient.getPhone());
        paramMap.put("isMarry", patient.getIsMarry());
        paramMap.put("provinceCode", patient.getProvinceCode());
        paramMap.put("cityCode", patient.getCityCode());
        paramMap.put("districtCode", patient.getDistrictCode());
        paramMap.put("address", patient.getAddress());

        // 封装联系人信息
        paramMap.put("contactsName", patient.getContactsName());
        paramMap.put("contactsCertificatesType", patient.getContactsCertificatesType());
        paramMap.put("contactsCertificatesNo", patient.getContactsCertificatesNo());
        paramMap.put("contactsPhone", patient.getContactsPhone());
        paramMap.put("timestamp", HttpRequestHelper.getTimestamp());

        // 将加密后的 signKey 封装到，参数发送给医院方
        paramMap.put("sign", signInfoVo.getSignKey());

        // 发送请求
        JSONObject result = HttpRequestHelper.sendRequest(paramMap, signInfoVo.getApiUrl() + "order/submitOrder");

        // 医院端使用同样的 MD5 算法校验 key 校验成功，状态码 200
        if (result.getInteger("code") == 200) {

            /*
            得到医院方封装的预约参数，进行封装
             */
            JSONObject jsonObject = result.getJSONObject("data");

            // 预约记录唯一标识（医院预约记录主键）
            String hosRecordId = jsonObject.getString("hosRecordId");

            // 预约序号
            Integer number = jsonObject.getInteger("number");

            // 取号时间
            String fetchTime = jsonObject.getString("fetchTime");

            // 取号地址
            String fetchAddress = jsonObject.getString("fetchAddress");

            // 更新订单
            orderInfo.setHosRecordId(hosRecordId);
            orderInfo.setNumber(number);
            orderInfo.setFetchTime(fetchTime);
            orderInfo.setFetchAddress(fetchAddress);
            baseMapper.updateById(orderInfo);

            // 更新排班可预约数
            Schedule schedule = hospitalClient.getScheduleById(scheduleId).getData();
            Integer availableNumber = schedule.getAvailableNumber() - 1;

            // 更新排班总预约数
            Integer reservedNumber = schedule.getReservedNumber();

            /*
             发送 mq 信息更新号源和短信通知
             */
            // 发送 mq 信息更新号源
            OrderMqVo orderMqVo = new OrderMqVo();
            orderMqVo.setScheduleId(scheduleId);
            orderMqVo.setReservedNumber(reservedNumber);
            orderMqVo.setAvailableNumber(availableNumber);

            // 短信提示
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(orderInfo.getPatientPhone());// 设置电话号码

            // 设置短信具体信息
            String reserveDate =
                    new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")
                            + (orderInfo.getReserveTime() == 0 ? "上午" : "下午");
            Map<String, Object> smsMap = new HashMap<String, Object>() {{
                put("title", orderInfo.getHosname() + "|" + orderInfo.getDepname() + "|" + orderInfo.getTitle());
                put("amount", orderInfo.getAmount());
                put("reserveDate", reserveDate);
                put("name", orderInfo.getPatientName());
                put("quitTime", new DateTime(orderInfo.getQuitTime()).toString("yyyy-MM-dd HH:mm"));
                put("msg", "预约成功!");
            }};
            msmVo.setParam(smsMap);
            orderMqVo.setMsmVo(msmVo);
            rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_ORDER, MQConst.ROUTING_ORDER, orderMqVo);
        } else {
            throw new YyghException(result.getString("message"), ResultCodeEnum.FAIL.getCode());
        }
        System.out.println(JSONObject.toJSONString(orderInfo));
        return orderInfo.getId();
    }

    /**
     * 根据 orderId 获取订单详情信息
     *
     * @param orderId 订单详情
     * @return 返回 ordeerInfo 对象
     */
    @Override
    public OrderInfo getOrderDetail(Long orderId) {
        OrderInfo orderInfo = orderInfoMapper.getOrderInfoById(orderId);

        String orderStatusName = OrderStatusEnum.getStatusNameByStatus(orderInfo.getOrderStatus());

        orderInfo.getParam().put("orderStatusName", orderStatusName);
        return orderInfo;
    }

    /**
     * 分页条件查询订单列表
     *
     * @param page         当前页
     * @param limit        每页记录数
     * @param orderQueryVo 条件查询对象
     * @return 返回分页对象
     */
    @Override
    public Page<OrderInfo> getOrderList(Long page, Long limit, OrderQueryVo orderQueryVo) {

        Page<OrderInfo> orderInfoPage = new Page<>(page, limit);

        QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();

        wrapper.eq("user_id", orderQueryVo.getUserId());// 当前登录用户的 id

        // orderQueryVo 获取条件值
        String name = orderQueryVo.getKeyword(); // 医院名称
        Long patientId = orderQueryVo.getPatientId(); // 就诊人名称
        String orderStatus = orderQueryVo.getOrderStatus(); // 订单状态
        String reserveDate = orderQueryVo.getReserveDate();// 安排时间
        String createTimeBegin = orderQueryVo.getCreateTimeBegin();
        String createTimeEnd = orderQueryVo.getCreateTimeEnd();

        // 对条件值进行非空判断
        if (!StringUtils.isEmpty(name)) {
            wrapper.like("hosname", name);
        }
        if (!StringUtils.isEmpty(patientId)) {
            wrapper.eq("patient_id", patientId);
        }
        if (!StringUtils.isEmpty(orderStatus)) {
            wrapper.eq("order_status", orderStatus);
        }
        if (!StringUtils.isEmpty(reserveDate)) {
            wrapper.ge("reserve_date", reserveDate);
        }
        if (!StringUtils.isEmpty(createTimeBegin)) {
            wrapper.ge("create_time", createTimeBegin);
        }
        if (!StringUtils.isEmpty(createTimeEnd)) {
            wrapper.le("create_time", createTimeEnd);
        }

        Page<OrderInfo> orderPage = baseMapper.selectPage(orderInfoPage, wrapper);

        // 设置支付的状态信息
        for (OrderInfo orderInfo : orderPage.getRecords()) {
            orderInfo.getParam().put("orderStatusName", OrderStatusEnum
                    .getStatusNameByStatus(orderInfo.getOrderStatus()));
        }

        return orderPage;
    }

    /**
     * 取消订单
     *
     * @param orderId
     */
    /*
    JDK1.8.0_291 在使用【com.github.wxpay.sdk 0.03】调用微信的取消订单接口时，会出现 SSLHandleException 的问题
    解决办法:
    删除 jdk 和 jre 中的 lib/security 目录下的 java.security 文件中:
     【jdk.tls.disabledAlgorithms=SSLv3, TLSv1.1, TLSv1, RC4, DES, MD5withRSA, \】中的 TLSv1.1 部分
     */
    @Override
    public Boolean cancelOrder(Long orderId) {
        // 根据订单 id 获取订单信息
        OrderInfo orderInfo = orderInfoMapper.getOrderInfoById(orderId);

        // 如果医院规定的退号时间在取消订单时间之前，不能取消订单
        if (new DateTime(orderInfo.getQuitTime()).isBeforeNow()) {
            throw new YyghException(ResultCodeEnum.CANCEL_ORDER_NO);// 不能取消预约
        }

        //

        /*
        平台先进行取消订单操作
         */
        RefundInfo refundInfo = null;// 退款记录对象
        boolean isRefund = false;// 平台是否退款取消预约成功
        // 是否支付
        if (orderInfo.getOrderStatus() == OrderStatusEnum.PAID.getStatus()) {

            // 微信退款
            Boolean refund = weixinService.refund(orderInfo.getId());

            // 退款成功，保存退款记录
            if (refund) {
                PaymentInfo paymentInfo = payInfoServicel.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());
                refundInfo = refundService.saveRefundInfo(paymentInfo);

                // 更新【支付记录(已退款)】 & 【订单(取消预约)】的状态
                if (null != refundInfo) {
                    orderInfo.setOrderStatus(OrderStatusEnum.CANCLE.getStatus());
                    paymentInfo.setPaymentStatus(PaymentStatusEnum.REFUND.getStatus());

                    // 更数据库中记录
                    orderInfoMapper.updateById(orderInfo);
                    payInfoServicel.updateById(paymentInfo);
                    isRefund = true;
                }
            } else {
                // 退款、取消预约失败
                throw new YyghException(ResultCodeEnum.CANCEL_ORDER_FAIL);
            }
        } else {
            // 没有支付的订单，直接更数据库中记录
            int i = orderInfoMapper.updateById(orderInfo);

            PaymentInfo paymentInfo =
                    payInfoServicel.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());
            boolean b = payInfoServicel.updateById(paymentInfo);

            // 必须订单和支付记录都更新成功
            if (b && i > 0) {
                isRefund = true;
            } else {
                throw new YyghException(ResultCodeEnum.DATA_ERROR);
            }
        }

        /*
         平台退款成功，医院更新退款记录
         */
        boolean isHospRefund = false;// 医院是否退款成功

        if (isRefund) {
            // 调用医院接口，更新订单状态
            Map<String, Object> paramMap = new HashMap<>();

            // 远程调用获取医院设置信息(signKey,apiUrl)
            SignInfoVo signInfoVo = hospitalClient.getSignInfoVo(orderInfo.getHoscode());
            paramMap.put("signKey", signInfoVo.getSignKey());
            paramMap.put("number", orderInfo.getNumber());

            // 请求医院接口
            JSONObject result =
                    HttpRequestHelper.sendRequest(paramMap, signInfoVo.getApiUrl() + "order/updateCancelStatus");

            // 医院更新退款记录成功
            if (result.getInteger("code") == 200) {
                isHospRefund = true;
            }
        }
        if (isHospRefund && isRefund) {
            /*
             更新号源信息 & 发送短信
             */
            // 更新排班可预约数
            String scheduleId = orderInfo.getHos_schedule_id();
            Schedule schedule = hospitalClient.getScheduleById(scheduleId).getData();
            Integer availableNumber = schedule.getAvailableNumber() + 1;

            // 更新排班总预约数
            Integer reservedNumber = schedule.getReservedNumber();

            /*
             发送 mq 信息更新号源
             */
            OrderMqVo orderMqVo = new OrderMqVo();

            // 封装短信提示 vo 对象
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(orderInfo.getPatientPhone());// 电话号码
            Map<String, Object> smsMap = new HashMap<String, Object>() {{
                put("msg", "取消预约成功!");
            }};// 短信内容
            msmVo.setParam(smsMap);

            //封装 Mq 相关参数
            orderMqVo.setMsmVo(msmVo);
            orderMqVo.setScheduleId(schedule.getId());
            orderMqVo.setReservedNumber(reservedNumber);
            orderMqVo.setAvailableNumber(availableNumber);

            rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_ORDER, MQConst.ROUTING_ORDER, orderMqVo);

            return true;
        }
        return false;
    }

    /**
     * 就诊当天八点，就诊短信提醒
     */
    @Override
    public void patientTips() {
        QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();

        wrapper.eq("reserve_date", new DateTime().toString("yyyy-MM-dd")); // 当天为就诊日期
        wrapper.ne("order_status", OrderStatusEnum.CANCLE.getStatus());// 用户的订单状态不等于 -1 ,也就是用户没有取消预约

        // 得到当天所有就诊人的集合
        List<OrderInfo> orderInfos = baseMapper.selectList(wrapper);

        // 遍历每个就诊人向其发送就诊短信
        OrderMqVo orderMqVo = new OrderMqVo();
        for (OrderInfo orderInfo : orderInfos) {
            // 封装短信提示 vo 对象
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(orderInfo.getPatientPhone());// 电话号码

            // 短信内容
            String msg = orderInfo.getReserveTime() == 0 ? "上午" : "下午" + "|"
                    + orderInfo.getHosname() + "|" + orderInfo.getDepname() + "|" + orderInfo.getTitle() + "|"
                    + "就诊人:" + orderInfo.getPatientName();
            Map<String, Object> smsMap = new HashMap<String, Object>() {{
                put("msg", msg);
            }};
            msmVo.setParam(smsMap);

            //封装 Mq 相关参数
            orderMqVo.setMsmVo(msmVo);
            // 发送就医提醒的 mq 消息,该消息被 service-msm 中的短信监听器监听
            rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_MSM, MQConst.ROUTING_MSM_ITEM, msmVo);
        }
    }

    /**
     * 订单的预约统计
     *
     * @param orderCountQueryVo
     * @return
     */
    @Override
    public Map<String, Object> getOrderCountMap(OrderCountQueryVo orderCountQueryVo) {

        List<OrderCountVo> orderCountVoList = orderInfoMapper.selectOrderCount(orderCountQueryVo);

        // 获取 x | y 轴需要的数据
        List<String> xList = new ArrayList<>(); // x 轴
        List<Integer> yList = new ArrayList<>(); // y 轴

        for (OrderCountVo orderCountVo : orderCountVoList) {
            xList.add(orderCountVo.getReserveDate());
            yList.add(orderCountVo.getCount());
        }

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("xList", xList);
        resultMap.put("yList", yList);

        return resultMap;
    }
}
